//  Inside Microsoft Exchange 2007 Web Services 
//  Chapter 16
//
//  Copyright (c) 2007 David Sterling, Ben Spain, Mark Taylor, Huw Upshall, Michael Mainer.  
//  All rights reserved.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using ProxyHelpers.EWS;
using System.Diagnostics;

namespace BuildSyncFolderTree
{
    class Program
    {
        static void Main(string[] args)
        {
            ExchangeServiceBinding binding = new ExchangeServiceBinding();
            binding.Url = "https://yourServer/ews/Exchange.asmx";
            binding.Credentials = new NetworkCredential("username", "password", "domain");

            DrawFullHierarchy(binding);
        }

        /// <summary>
        /// Represents the type of change (Listing 16-18)
        /// </summary>
        public enum FolderSyncChangeType
        {
            Create,
            Update,
            Delete,
        }

        /// <summary>
        /// Wrapper class to hold the folder and its list of children (Listing 16-18)
        /// </summary>
        public class FolderSyncChange
        {
            private BaseFolderType folder;
            private FolderSyncChangeType changeType;
            private List<FolderSyncChange> children;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="folder">The folder</param>
            /// <param name="changeType">Type of change (create, update)</param>
            /// 
            public FolderSyncChange(BaseFolderType folder, FolderSyncChangeType changeType)
            {
                this.folder = folder;
                this.changeType = changeType;
                this.children = new List<FolderSyncChange>();
            }

            /// <summary>
            /// Accessor for folder
            /// </summary>
            /// 
            public BaseFolderType Folder
            {
                get { return this.folder; }
            }

            /// <summary>
            /// Accessor for change type
            /// </summary>
            /// 
            public FolderSyncChangeType ChangeType
            {
                get { return this.changeType; }
            }

            /// <summary>
            /// Accessor for children
            /// </summary>
            /// 
            public List<FolderSyncChange> Children
            {
                get { return this.children; }
            }

            /// <summary>
            /// Returns true if there are any children for this folder
            /// </summary>
            /// 
            public bool HasChildren
            {
                get { return this.children.Count > 0; }
            }
        }

        /// <summary>
        /// Calls SyncFolderHierarchy and the converts the response into a tree structure
        /// </summary>
        /// <param name="binding">Binding to use for call</param>
        /// <param name="responseShape">Response shape</param>
        /// <param name="newSyncState">New sync state</param>
        /// <returns>List of top level changes (immediate children of IPM Subtree)</returns>
        /// 
        public static List<FolderSyncChange> SyncFolderHierarchyWithExpansion(
                                                    ExchangeServiceBinding binding,
                                                    FolderResponseShapeType responseShape,
                                                    out string newSyncState)
        {
            // First make our SyncFolderHeirarchy call.
            //
            SyncFolderHierarchyType request = new SyncFolderHierarchyType();
            request.FolderShape = responseShape;
            SyncFolderHierarchyResponseType response = binding.SyncFolderHierarchy(request);
            SyncFolderHierarchyResponseMessageType responseMessage =
                 response.ResponseMessages.Items[0] as
                       SyncFolderHierarchyResponseMessageType;

            if (responseMessage.ResponseCode != ResponseCodeType.NoError)
            {
                throw new Exception("SyncFolderHierarchy failed with response code: " +
                      responseMessage.ResponseCode.ToString());
            }

            Dictionary<string, FolderSyncChange> parentFolderMap =
                  new Dictionary<string, FolderSyncChange>();

            // set the sync state
            //
            newSyncState = responseMessage.SyncState;
            List<FolderSyncChange> result = new List<FolderSyncChange>();

            // Unfortunately, Changes is exposes as an object array since there is no other common
            // base class between the various change types. Since this is an initial sync, 
            // we will have no delete changes.
            //
            for (int index = 0; index < responseMessage.Changes.Items.Length; index++)
            {
                SyncFolderHierarchyCreateOrUpdateType change =
                   responseMessage.Changes.Items[index] as SyncFolderHierarchyCreateOrUpdateType;
                if (change != null)
                {
                    // We need the parent folder id to build our hierarchy, so make sure the 
                    // response shape they supplied retrieved the parent folder id.
                    //
                    if (change.Item.ParentFolderId == null)
                    {
                        throw new ArgumentException("ResponseShape must include the ParentFolderId",
                          "responseShape");
                    }
                    FolderSyncChange folderChange;

                    // we have either a create or update change.  Need to look at the ItemsElementName 
                    // to determine which it is.
                    switch (responseMessage.Changes.ItemsElementName[index])
                    {
                        case ItemsChoiceType1.Create:
                            folderChange = new FolderSyncChange(change.Item, FolderSyncChangeType.Create);
                            break;
                        case ItemsChoiceType1.Update:
                            folderChange = new FolderSyncChange(change.Item, FolderSyncChangeType.Update);
                            break;
                        default:
                            Debug.Assert(
                                false,
                                "We should not hit this since we have an instance of " +
                                "SyncFolderHierarchyCreateOrUpdateType.");
                            return null;
                    }

                    // Now, figure out which parent to put the folder change under.
                    //
                    FolderSyncChange parent;
                    if (parentFolderMap.TryGetValue(folderChange.Folder.ParentFolderId.Id, out parent))
                    {
                        parent.Children.Add(folderChange);
                    }
                    else
                    {
                        // this is contained in the root, so therefore it goes into the response list 
                        // directly.
                        //
                        result.Add(folderChange);
                    }

                    // Now, add this to the dictionary as this folder could have its own subfolders.
                    //
                    parentFolderMap.Add(folderChange.Folder.FolderId.Id, folderChange);
                }
            }
            return result;
        }

        /// <summary>
        /// Writes the tree structure of a given FolderSyncChange to the console
        /// (Listing 16-20)
        /// </summary>
        /// <param name="toDraw">FolderSyncChange to draw including its children</param>
        /// <param name="offset">Current tab offset</param>
        /// 
        public static void DrawFolderSyncHierarchy(FolderSyncChange toDraw, int offset)
        {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < offset; i++)
            {
                builder.Append("\t");
            }
            string prefixTabs = builder.ToString();

            // Draw this folder
            Console.WriteLine("{0}{1} ({2})",
                     prefixTabs,
                     toDraw.Folder.DisplayName,
                     toDraw.Children.Count);

            // If the folder has children, recursively call DrawFolderSyncHierarchy
            //
            if (toDraw.HasChildren)
            {
                offset++;
                foreach (FolderSyncChange child in toDraw.Children)
                {
                    DrawFolderSyncHierarchy(child, offset);
                }
            }
        }

        /// <summary>
        /// Draw the full sync folder hierarchy (Listing 16-21)
        /// </summary>
        /// <param name="binding">Binding to use for the call</param>
        /// 
        public static void DrawFullHierarchy(ExchangeServiceBinding binding)
        {
            string syncState;
            List<FolderSyncChange> result = SyncFolderHierarchyWithExpansion(
                                binding,
                                new FolderResponseShapeType(
                                          DefaultShapeNamesType.AllProperties),
                                out syncState);

            Console.WriteLine("IPM Subtree (msgfolderroot)");
            foreach (FolderSyncChange change in result)
            {
                DrawFolderSyncHierarchy(change, 1 /*offset*/);
            }
        }
    }
}
